using System;
using System.Threading;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Kollektiv.Config;
using Kollektiv.Extension;
using System.Linq;
using System.Reflection;

namespace Kollektiv.DB
{
	public abstract class DataManager : IDisposable
	{
		/// <summary>
		/// Gets if there is an active transaction.
		/// </summary>
		/// <value>
		/// <c>true</c> if there is an active transaction, otherwise <c>false</c>.
		/// </value>
		public abstract bool ActiveTransaction{ get; }
		
		private DataService service;
		private Dictionary<Type, DataRepository > repositories = new Dictionary<Type, DataRepository> ();
				
		internal void Initialize (DataService service, bool create)
		{
			this.service = service;
			
			this.onInitialize (create);
		}
		
		/// <summary>
		/// Called at the initialization of the data manager.
		/// </summary>
		/// <param name='create'>
		/// Should it create the database schema?
		/// </param>
		protected virtual void onInitialize (bool create)
		{
			this.InitRepositories ();
		}
		
		/// <summary>
		/// Gets a config value.
		/// </summary>
		/// <returns>
		/// The config value.
		/// </returns>
		/// <param name='key'>
		/// The config value key.
		/// </param>
		protected string GetConfigValue (string key)
		{
			return this.service.instanceConfig.Values.Where (cv => cv.Key == key).Select (cv => cv.Value).FirstOrDefault ();
		}
		
		private	void InitRepositories ()
		{
			if (this.service.instanceConfig.RepositoryAssemblies != null) {
                foreach (Assembly respositoryAssembly in this.service.instanceConfig.RepositoryAssemblies)
                {
                    foreach (Type type in respositoryAssembly.GetTypes().Where(x => x.IsSubclassOf(typeof(DataRepository))))
                    {
                        DataRepository repository = (DataRepository)Activator.CreateInstance(type, this);
                        if (this.onCreateRepository(repository))
                            this.repositories.Add(type, repository);
                    }
                }
			}
		}
		
		/// <summary>
		/// Called at the creation of a data repository.
		/// </summary>
		/// <returns>
		/// <c>false</c> if the creation of the repository should be interrupted, otherwise <c>true</c>.
		/// </returns>
		/// <param name='repository'>
		/// The repository gets created.
		/// </param>
		protected virtual bool onCreateRepository (DataRepository repository)
		{
			return true;
		}
		
		/// <summary>
		/// Gets a repository.
		/// </summary>
		/// <returns>
		/// The repository.
		/// </returns>
		/// <typeparam name='T'>
		/// The 1st type parameter.
		/// </typeparam>
		public T GetRepository<T> () where T : DataRepository
		{
			if (this.repositories.ContainsKey (typeof(T)))
				return (T)this.repositories [typeof(T)];
			else
				return null;
		}
		
		/// <summary>
		/// Begin a transaction.
		/// </summary>
		public abstract void BeginTransaction ();
		
		/// <summary>
		/// Commit an active transaction.
		/// </summary>
		public abstract void CommitTransaction ();
		
		/// <summary>
		/// Rollback an active transaction.
		/// </summary>
		public abstract void RollbackTransaction ();
		
		/// <summary>
		/// Saves or updates a data object.
		/// </summary>
		/// <param name='obj'>
		/// The affecting data object.
		/// </param>
		public abstract void SaveOrUpdate (DataObject obj);
		
		/// <summary>
		/// Deletes a data object.
		/// </summary>
		/// <param name='obj'>
		/// The affecting data object.
		/// </param>
		public abstract void Delete (DataObject obj);
		
		/// <summary>
		/// Gets a query of all data objects of a given type.
		/// </summary>
		/// <typeparam name='T'>
		/// The data object type.
		/// </typeparam>
		public abstract IQueryable<T> Get<T> ()where T:DataObject;
		
		/// <summary>
		/// Performs application-defined tasks associated with freeing or resetting resources.
		/// </summary>
		/// <remarks>
		/// Call <see cref="Dispose"/> when you are finished using the <see cref="Kollektiv.DB.DataManager"/>. The
		/// <see cref="Dispose"/> method leaves the <see cref="Kollektiv.DB.DataManager"/> in an unusable state. After calling
		/// <see cref="Dispose"/>, you must release all references to the <see cref="Kollektiv.DB.DataManager"/> so the
		/// garbage collector can reclaim the memory that the <see cref="Kollektiv.DB.DataService"/> was occupying.
		/// </remarks>
		public abstract void Dispose ();
	}
}

